1 //+-----------------------------------------------------------------------
3 // Copyright (c) Microsoft Corporation. All rights reserved.
9 // 2007/03/30 [....] Created
10 // 2007/09/20 [....] Ported Windows->DevDiv. See SourcesHistory.txt.
12 //------------------------------------------------------------------------
14 #include "precompiled.hxx"
15 #include "CookieShim.hxx"
16 #include "HostSupport.h"
17 #include "OleDocument.hxx"
18 #include "urlmoninterop.hxx"
19 #include "DllMain.hxx" // g_pOleDoc, g_mainThreadId
20 #include "..\Detours\Detours.h"
23 // CAutoSetWin32Error helps correctly set the last error from functions using objects with destructors.
24 // A dtor may do cleanup and thus call Win32 functions that overwrite what was previously set with
25 // SetLastError(). Declare the CAutoSetWin32Error instance before any other stack objects in a function.
26 // This way its dtor will be called after all others and will set the last Win32 error.
27 class CAutoSetWin32Error
32 m_error(ERROR_SUCCESS
) { }
33 void SetLastError(DWORD error
)
39 // By convention, SetLastError() is called only when returning an error.
40 if(m_error
!= ERROR_SUCCESS
)
42 ::SetLastError(m_error
);
47 ////////////////////////////////////////////////////////////////////////////////////////
50 // WinInet must not be delay-loaded in order for these initializations to work correctly.
51 DWORD (WINAPI
*CCookieShim::s_pfInternetSetCookieEx
)(
52 LPCWSTR
, __in_opt LPCWSTR
, LPCWSTR
, DWORD
, __in_opt DWORD_PTR
) = &InternetSetCookieExW
;
53 BOOL (WINAPI
*CCookieShim::s_pfInternetGetCookieEx
)(
54 LPCWSTR
, __in_opt LPCWSTR
, __out_ecount_opt(*pcchData
) LPWSTR
, __inout LPDWORD pcchData
, DWORD
, LPVOID
) = &InternetGetCookieExW
;
56 bool CCookieShim::s_isApplicationThirdParty
;
58 HRESULT
CCookieShim::Init(bool isTopLevel
, __in_opt LPCWSTR topLevelUri
)
62 // Detours can be found on http://toolbox. It has a nice help file with it.
63 // It is enough to detour InternetSetCookieExW & InternetGetCookieExW because that's what the managed code
65 CHECK_ERROR_CODE(DetourTransactionBegin());
66 CHECK_ERROR_CODE(DetourUpdateThread(GetCurrentThread()));
67 CHECK_ERROR_CODE(DetourAttach((void**)&s_pfInternetSetCookieEx
, InternetSetCookieExDetour
));
68 CHECK_ERROR_CODE(DetourAttach((void**)&s_pfInternetGetCookieEx
, InternetGetCookieExDetour
));
69 CHECK_ERROR_CODE(DetourTransactionCommit());
71 s_isApplicationThirdParty
= false;
74 if(!topLevelUri
|| !topLevelUri
[0])
75 { // browser didn't tell us => probably crossing security zones
76 s_isApplicationThirdParty
= true;
80 // Test whether the startup URI is 3rd-party with respect to the top-level URI.
81 s_isApplicationThirdParty
= IsThirdPartyUri(topLevelUri
) != S_FALSE
;
89 void CCookieShim::Uninit()
91 // Undetouring on shutdown is needed specifically to avoid DevDiv bug 161831: WMNetMgr.dll (part of the
92 // Windows Media control) somehow manages to make an (asynchronous) call to InternetGetCookieEx() after
94 if(s_pfInternetSetCookieEx
!= InternetSetCookieEx
) // detoured?
96 if(DetourTransactionBegin() == NOERROR
&&
97 DetourUpdateThread(GetCurrentThread()) == NOERROR
&&
98 DetourDetach((void**)&s_pfInternetSetCookieEx
, InternetSetCookieExDetour
) == NOERROR
&&
99 DetourDetach((void**)&s_pfInternetGetCookieEx
, InternetGetCookieExDetour
) == NOERROR
)
101 DetourTransactionCommit();
110 DWORD WINAPI
CCookieShim::InternetSetCookieExDetour(
111 LPCWSTR lpszUrl
, __in_opt LPCWSTR lpszCookieName
, LPCWSTR lpszCookieData
, DWORD flags
, __in_opt DWORD_PTR P3PHeader
)
113 CAutoSetWin32Error autoError
;
114 if(!(lpszUrl
&& *lpszUrl
&& lpszCookieData
&& *lpszCookieData
))
116 autoError
.SetLastError(ERROR_INVALID_PARAMETER
);
119 if(flags
& ~(INTERNET_COOKIE_THIRD_PARTY
| INTERNET_COOKIE_EVALUATE_P3P
))
122 autoError
.SetLastError(ERROR_INVALID_PARAMETER
);
127 bool isThirdParty
= (flags
& INTERNET_COOKIE_THIRD_PARTY
) != 0;
130 // 3rd-party status has to be forced in two situations:
131 // - WebOC hosted in an XBAP. The WebOC always thinks it's a top-level browser, but the XBAP as a whole
132 // may be 3rd party to the top-level document.
133 // - Media fetched from outside the site of origin. The managed CookieHandler doesn't pass the 3rd party
134 // bit, but it assumes that the call to InternetSetCookieEx will be intercepted here and the bit added
136 hr
= IsThirdPartyUri(lpszUrl
);
139 autoError
.SetLastError(ERROR_GEN_FAILURE
);
142 isThirdParty
= hr
== S_OK
;
145 CComPtr
<IHostBrowser
> spHostBrowser
;
146 if(g_pOleDoc
->GetWebBrowserForCurrentThread(&spHostBrowser
) != S_OK
)
148 autoError
.SetLastError(ERROR_VC_DISCONNECTED
); // "The session was canceled."
151 hr
= spHostBrowser
->SetCookie(
152 lpszUrl
, lpszCookieName
, lpszCookieData
, isThirdParty
, reinterpret_cast<LPCWSTR
>(P3PHeader
));
155 //! Assuming that the implementation of SetCookie returns E_ACCESSDENIED for a rejected cookie or
156 // otherwise HRESULT_FROM_WIN32.
157 if(hr
== E_ACCESSDENIED
)
158 return COOKIE_STATE_REJECT
;
159 autoError
.SetLastError(hr
& 0xFFFF);
163 BOOL WINAPI
CCookieShim::InternetGetCookieExDetour(
164 LPCWSTR pUri
, __in_opt LPCWSTR pCookieName
, __out_ecount_opt(*pcchData
) LPWSTR pCookieData
, __inout LPDWORD pcchData
, DWORD flags
, LPVOID
)
166 CAutoSetWin32Error autoError
;
167 if(!(pUri
&& *pUri
&& pcchData
))
169 autoError
.SetLastError(ERROR_INVALID_PARAMETER
);
172 // pCookieData can be null on entry. Then only the size of the buffer needed for the cookie data is returned.
173 if(flags
& ~INTERNET_COOKIE_THIRD_PARTY
)
176 autoError
.SetLastError(ERROR_INVALID_PARAMETER
);
181 bool isThirdParty
= (flags
& INTERNET_COOKIE_THIRD_PARTY
) != 0;
184 //[See explanation in InternetSetCookieExDetour.]
185 hr
= IsThirdPartyUri(pUri
);
188 autoError
.SetLastError(ERROR_GEN_FAILURE
);
191 isThirdParty
= hr
== S_OK
;
194 CComPtr
<IHostBrowser
> spHostBrowser
;
195 if(g_pOleDoc
->GetWebBrowserForCurrentThread(&spHostBrowser
) != S_OK
)
197 autoError
.SetLastError(ERROR_VC_DISCONNECTED
); // "The session was canceled."
201 hr
= spHostBrowser
->GetCookie(pUri
, pCookieName
, isThirdParty
, &cookieData
);
204 autoError
.SetLastError(hr
& 0xFFFF);
208 unsigned cchData
= cookieData
.Length()+1; // +1 for terminating '\0'
209 if(cchData
<= 1 || cchData
> CCH_COOKIE_MAX
)
211 autoError
.SetLastError(ERROR_INVALID_DATA
);
214 // Make sure the terminating '\0' is not included in the BSTR's character count. (This is possible and
215 // valid in principle but unexpected in most cases.)
216 ASSERT(cookieData
[cchData
-2] && !cookieData
[cchData
-1]);
220 // Return space needed to store cookie data.
221 // *2: Kid you not, this is what WinINet returns! (inetcore\wininet\http\cookie.cxx)
222 // And only in this case (and when returning ERROR_INSUFFICIENT_BUFFER--like below). (!#$#$)
223 // And the ClickOnce SystemNetDownloader is aware of this doubling and allocates only half that many
225 *pcchData
= cchData
*2;
229 if(*pcchData
< cchData
)
231 *pcchData
= cchData
*2; // see above...
232 autoError
.SetLastError(ERROR_INSUFFICIENT_BUFFER
);
235 StringCchCopy(pCookieData
, *pcchData
, cookieData
);
236 *pcchData
= cchData
; // WinINet includes the final '\0' in the count.
241 HRESULT
CCookieShim::GetInternetSecurityManagerForCurrentThread(__deref_out IInternetSecurityManager
**ppSecMgr
)
243 if(GetCurrentThreadId() == g_mainThreadId
)
244 return UrlmonInterop::GetSecurityManager(ppSecMgr
);
246 static IClassFactory
*pSecMgrFactory
;
249 HRESULT hr
= CoGetClassObject(
250 CLSID_InternetSecurityManager
, CLSCTX_INPROC_SERVER
, 0,
251 IID_IClassFactory
, (void**)&pSecMgrFactory
);
255 return pSecMgrFactory
->CreateInstance(0, IID_IInternetSecurityManager
, (void**)ppSecMgr
);
258 HRESULT
CCookieShim::IsThirdPartyUri(LPCWSTR uri
)
261 if(s_isApplicationThirdParty
)
264 // Quick test for the startup URI
265 LPCWSTR startupUri
= g_pOleDoc
->GetStartupUri();
266 if(_wcsicmp(startupUri
, uri
) == 0)
269 // The same method is used here as in IE: Get the "security id" of both URIs and then call
270 // CompareSecurityIds(). Despite its name, CompareSecurityIds() actually tests for matching
271 // "minimal domains" only. For example, "http://www.abc.com" and "http://images.abc.com" are considered
272 // matching. But "http://abc.com.uk" and "http://cnn.com.uk" are not.
274 static UriSecurityId s_startupUriSId
;
276 CComPtr
<IInternetSecurityManager
> spSecMgr
;
277 CKHR(GetInternetSecurityManagerForCurrentThread(&spSecMgr
));
278 if(!s_startupUriSId
.id
[0]) // init once, lazily
280 CKHR(s_startupUriSId
.Set(startupUri
, spSecMgr
));
282 CKHR(sid2
.Set(uri
, spSecMgr
));
283 CKHR(CompareSecurityIds(s_startupUriSId
.id
, s_startupUriSId
.idLen
, sid2
.id
, sid2
.idLen
, 0));
284 hr
= hr
== S_OK
? S_FALSE
: S_OK
;